package com.fubowen.reader.components

import android.graphics.Paint
import android.graphics.Typeface
import android.text.TextPaint
import kotlin.math.floor

class TextPaging(
    var mHeight: Float,
    var mWidth: Float,
    var textSize: Float,
    var lineSpacingMultiplier: Float = 1.5f,
    var lineSpacingExtra: Float = 0f,
    var textStyle: Int = Typeface.BOLD,
    var bgColor: Int = -1,
    var color: Int = -1
) {
    private var fontMetrics = Paint.FontMetrics()
    private val textPaint = TextPaint()
    private var lineHeight = 0f
    private val pageList = mutableListOf<List<String>>()
    private val pageConfigCache = mutableMapOf<Int, PageConfig>()

    init {
        textPaint.textSize = textSize
        val suggestLineSpace = textPaint.getFontMetrics(fontMetrics)
        lineHeight = suggestLineSpace * lineSpacingMultiplier + lineSpacingExtra
    }

    fun setHeight(height: Float) {
        mHeight = height
    }

    fun setBackgroundColor(backgroundColor: Int) {
        bgColor = backgroundColor
    }

    fun setTextColor(textColor: Int) {
        color = textColor
    }

    fun setWidth(width: Float) {
        mWidth = width
    }

    fun getTextHeight(text: String): Float {
        val iterator = TextIterator(text)
        var count = 0
        while (iterator.hasNext()) {
            iterator.next(textPaint, mWidth)
            count++
        }
        return count * lineHeight
    }

    private fun toPageConfig(pageConf: List<Triple<Int, Space, Float>>) {
        pageConf.forEach {
            var conf = pageConfigCache[it.first]
            if (conf == null) {
                conf = PageConfig(it.first)
                pageConfigCache[it.first] = conf
            }
            when (it.second) {
                Space.HEIGHT_USED_SPACE -> conf.height = mHeight - it.third
                Space.HEIGHT_FREE_SPACE -> conf.height = it.third
            }
            conf.maxLineCount = floor(conf.height / lineHeight).toInt()
        }
    }

    private fun getPageConfig(pageIndex: Int): PageConfig {
        return pageConfigCache[pageIndex] ?: PageConfig(
            page = pageIndex,
            width = mWidth,
            height = mHeight,
            maxLineCount = floor(mHeight / lineHeight).toInt()
        )
    }

    fun <T> toPaging(
        text: String,
        vararg conf: Triple<Int, Space, Float>,
        transform: (List<String>, Int) -> T
    ): List<T> {
        pageList.clear()
        toPageConfig(conf.toList())
        var pageIndex = 1
        var pageConfig = getPageConfig(pageIndex)
        val iterator = TextIterator(text)
        var page = mutableListOf<String>()
        while (iterator.hasNext()) {
            val line = iterator.next(textPaint, pageConfig.width)
            page.add(line)
            if (page.size == pageConfig.maxLineCount) {
                pageList.add(page)
                pageConfig = getPageConfig(++pageIndex)
                page = mutableListOf()
            }
        }
        if (page.isNotEmpty()) {
            pageList.add(page)
        }
        return pageList.mapIndexed { index, items -> transform(items, index) }
    }

    class TextIterator(text: String) {

        private val builder = StringBuilder()
        private val measuredWidth = FloatArray(1)
        private var tempLineList = mutableListOf<String>()

        init {
            tempLineList.addAll(text.split("\n").map { "$it\n" })
        }

        fun hasNext() = tempLineList.isNotEmpty() || builder.isNotBlank()

        fun next(textPaint: TextPaint, width: Float): String {
            checkOrSet()
            val index = textPaint.breakText(builder.toString(), true, width, measuredWidth)
            val line = builder.substring(0, index)
            builder.delete(0, index)
            return line
        }

        private fun checkOrSet() {
            if (builder.isBlank()) {
                builder.append(tempLineList.removeAt(0))
            }
        }
    }

    data class PageConfig(
        var page: Int = 0,
        var width: Float = 0f,
        var height: Float = 0f,
        var maxLineCount: Int = 0
    )

    enum class Space {
        HEIGHT_FREE_SPACE,
        HEIGHT_USED_SPACE,
    }
}

